/*
This library was inspired by and derived from v1.4 pinChangeInt.h (http://arduino.cc/playground/Main/PinChangeInt)
which was inspired by and derived from "johnboiles" (it seems)
PCInt Arduino Playground example here: http://www.arduino.cc/playground/Main/PcInt

It provides an extension to the interrupt support for arduino by
adding pin change interrupts, giving a way for users to have
interrupts drive off of any pin that supports PCINT.

This version is a bit simpler than the above, at the expense of speed and memory usage.
It adds error checking and support for the Mega 2560

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

*/

#include "PCInterrupt.h"

#define DEBUG

#ifdef DEBUG
volatile int cnt = 0;
#endif

extern PCInterrupt myInt;
#define PORT_INDEX(pt) (pt - 2)

// Declare the PCINT Interrupt handlers
#if defined(PCINT0_vect)
ISR(PCINT0_vect){PCInterrupt::dispatchPCINT0();}
#endif

#if defined(PCINT1_vect)
ISR(PCINT1_vect){PCInterrupt::dispatchPCINT1();}
#endif

#if defined(PCINT2_vect)
ISR(PCINT2_vect){PCInterrupt::dispatchPCINT2();}
#endif

#if defined(PCINT3_vect)
ISR(PCINT3_vect){PCInterrupt::dispatchPCINT3();}
#endif

// Interrupt dispatch handlers - Map PCINT to Port (actually port Index)
inline void PCInterrupt::dispatchPCINT0() {  myInt.dispatchInterrupt( PORT_INDEX(2) );} // portb
inline void PCInterrupt::dispatchPCINT1() {  myInt.dispatchInterrupt( PORT_INDEX(3) );} // portc
inline void PCInterrupt::dispatchPCINT2() {  myInt.dispatchInterrupt( PORT_INDEX(4) );} // portd (and PORTK on Mega 2560)
inline void PCInterrupt::dispatchPCINT3() {  myInt.dispatchInterrupt( PORT_INDEX(5) );} // porte

// Dispatch the right handler for this pin interrupt
inline void PCInterrupt::dispatchInterrupt(int portIndex)  /* static */
{
    uint8_t val, changedPins;
    PCintPin* p;

#ifdef DEBUG
    cnt++;
#endif

    // Read data - using the right port register
    val = *(portInfo[portIndex]._portRegister);             // Read port (read the 8 pin values on this port)
    changedPins = val ^ portInfo[portIndex]._prevPortValue; // Calc which pins in port have changed
    portInfo[portIndex]._prevPortValue = val;               // Save port reg so next time we can see what changed

    // Go through all the pin structures to see if one of the pinMask matches this interrupt mask:
    // AND changedPins with & _pinMask from pin structure, if match, call function in Pin Structure
    p = pinHead;

    while (p != NULL) {
        if (changedPins & p->_pinMask)   // The changed bit matches our pin mask
        {
            if (p->mode == CHANGE) p->PCintFunc();     // Alert on any change
            else if (p->mode == RISING  && val & p->_pinMask) p->PCintFunc();  // Alert only on rising
            else if (p->mode == FALLING && !(val & p->_pinMask) ) p->PCintFunc();  // Alert only on falling
        }
        p = p->next;
    }
}

//! \param pin - The pin that will generate an interrupt.  Check pins_arduino.h for your hw to see which pins are allowed
//! \param mode - Should we alert on any CHANGE, or just RISING, or just FALLING
//! \param userFunc - the routine called when pin generates an interrupt

//! Attach specified interrupt handler to this pin.  Returns 1 if successful.  Create PCInterrupt myInt and call myInt.attachInterrupt(...);
int PCInterrupt::attachInterrupt(uint8_t pin, uint8_t mode, PCIntvoidFuncPtr userFunc)
{
    int res;
    uint8_t portIdx, interruptID;

    if (digitalPinToPCMSK(pin) == 0) return 0;  // Not a valid interrupt pin!

    pinMode(pin, INPUT);
    uint8_t port = digitalPinToPort(pin);

    // The ports run from 2-5 and PORT_INDEX maps those into the array as 0-3
    if (port == 11)  // kluge - to keep the array smaller, we map Mega 2560 Port 11 into port 4
        portIdx = PORT_INDEX(4);
    else
        portIdx = PORT_INDEX( port );

    portInfo[portIdx]._portRegister = portInputRegister(port);
    portInfo[portIdx]._prevPortValue = *(portInfo[portIdx]._portRegister);   // Save previous port value so we can track what changed.

    res = addPin(pin, mode, userFunc);

    // Enable interrupt for this port and pin mask
    if (res)
    {
        *digitalPinToPCICR(pin) |= _BV(digitalPinToPCICRbit(pin));
        *digitalPinToPCMSK(pin) |= _BV(digitalPinToPCMSKbit(pin));
        return 1;
    }
    else
        return 0;
}

// Add the pin related data we need to handle interrupts.  Pins are kept in linked list
int PCInterrupt::addPin(uint8_t pin, uint8_t mode, PCIntvoidFuncPtr userFunc)
{
    // Create pin newPin structure to hold interrupt info for this pin
    PCintPin* newPin = new PCintPin;
    if (newPin == NULL) return 0;

    newPin->mode = mode;
    newPin->next = NULL;
    newPin->_pinMask = digitalPinToBitMask(pin); // pin mask
    newPin->PCintFunc=userFunc;

    // Add to linked list, starting with firstPin
    if (pinHead == NULL) pinHead=newPin;
    else {
        PCintPin* p_tmp = pinHead;
        while (p_tmp->next != NULL)
        {  // Move through list to last item
            p_tmp=p_tmp->next;
        }
        p_tmp->next = newPin;
    }
    return(1);
}


PCintPin::PCintPin()
{
    PCintFunc = ( (PCIntvoidFuncPtr) NULL);
}


PCInterrupt::PCInterrupt(){pinHead=NULL;}
